﻿using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Xml;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Collections;
using System.Collections.ObjectModel;

namespace MicroLink.StsDev2008AddIn.DeploymentFiles
{
    internal class ManifestXmlBuilder
    {
        private ManifestXmlBuilder() { }

        private const string nsSP = "http://schemas.microsoft.com/sharepoint/";

        internal static void Create(string projectPath, string buildPath, string targetName)
        {
            string[] projectFiles = Directory.GetFiles(projectPath, "*.csproj");
            string projectFile;
            if (projectFiles.Length <= 0)
                projectFile = string.Format(@"{0}\{1}.csproj", projectPath, targetName);
            else
                projectFile = projectFiles[0];

            Create(
                projectPath
                , buildPath
                , targetName
                , projectFile
                );
        }

        internal static void Create(string projectPath, string buildPath, string targetName, string projectFile)
        {

            Create(
                buildPath
                , targetName
                , projectFile
                , string.Format(@"{0}\RootFiles\TEMPLATE\FEATURES", projectPath)
                , string.Format(@"{0}\RootFiles\TEMPLATE", projectPath)
                , string.Format(@"{0}\RootFiles\ISAPI", projectPath)
                , string.Format(@"{0}\RootFiles\RESOURCES", projectPath)
                , true
                , "GlobalAssemblyCache"
                , false
                , true
                );
        }

        private static void Create(
            string buildPath
            , string targetName
            , string projectFile
            , string featuresFolder
            , string templateFolder
            , string isapiFolder
            , string resourcesFolder
            , bool assemblyDeployment
            , string assemblyDeploymentLocation
            , bool assemblyContainsSafeControls
            , bool IISRESET)
        {
            StreamWriter manifestStream = new StreamWriter(buildPath + @"\manifest.xml");
            XmlWriterSettings settings = new XmlWriterSettings();
            settings.Encoding = Encoding.UTF8;
            settings.Indent = true;
            settings.NewLineOnAttributes = false;
            settings.NewLineHandling = NewLineHandling.None;
            XmlWriter writer = XmlWriter.Create(manifestStream, settings);
            writer.WriteStartDocument();

            writer.WriteComment("Manifest created at " + DateTime.Now.ToString());
            writer.WriteStartElement("Solution", nsSP);

            Assembly targetAssembly = Assembly.Load(File.ReadAllBytes(buildPath + @"\" + targetName + ".dll"));
            //Assembly targetAssembly = Assembly.LoadFile(projectPath + buildPath + @"\" + targetName + ".dll");
            GuidAttribute targetAttributeGuid = Attribute.GetCustomAttribute(targetAssembly, typeof(GuidAttribute)) as GuidAttribute;
            if (targetAttributeGuid == null)
                throw new NullReferenceException("The assembly does not have a GUID specified");
            targetAssembly = null;

            // add solution ID
            writer.WriteAttributeString("SolutionId", targetAttributeGuid.Value);
            writer.WriteAttributeString("ResetWebServer", IISRESET.ToString());

            targetAttributeGuid = null;

            if (Directory.Exists(featuresFolder))
            {
                // write feature manifests
                writer.WriteComment("Feature Manifest files");
                writer.WriteStartElement("FeatureManifests");
                // write FEATURE Files
                foreach (string directory in Directory.GetDirectories(featuresFolder))
                {
                    DirectoryInfo di = new DirectoryInfo(directory);
                    if ((di.Attributes & FileAttributes.Hidden) != 0)
                    {
                        WriteFeatureManifestFile(writer, directory.Substring((featuresFolder + @"\").Length) + @"\feature.xml");
                    }
                }
                writer.WriteEndElement(); // </FeatureManifest>
            }

            if (Directory.Exists(templateFolder))
            {
                // write TEMPLATE Files
                writer.WriteComment("TEMPLATE files");
                writer.WriteStartElement("TemplateFiles");
                WriteTemplateFiles(writer, templateFolder, templateFolder);
                writer.WriteEndElement(); // </TemplateFiles>
            }

            if (Directory.Exists(isapiFolder) || Directory.Exists(resourcesFolder))
            {
                // write Root Files
                writer.WriteComment("Root files");
                writer.WriteStartElement("RootFiles");
                if (Directory.Exists(isapiFolder))
                    WriteRootFiles(writer, isapiFolder, isapiFolder);
                if (Directory.Exists(resourcesFolder))
                    WriteRootFiles(writer, resourcesFolder, resourcesFolder);
                writer.WriteEndElement(); // </RootFiles>
            }

            if (assemblyDeployment)
            {
                WriteAssemblies(
                    writer
                    , string.Format("{0}.dll", targetName)
                    , buildPath
                    , projectFile
                    , assemblyDeploymentLocation);
            }

            writer.WriteEndElement(); // </Solution>
            writer.WriteEndDocument();
            writer.Close();
            manifestStream.Close();
        }

        private static void WriteAssemblies(
            XmlWriter writer
            , string targetDll
            , string buildPath
            , string projectFile
            , string assemblyDeploymentLocation)
        {
            // write Assembly Files
            writer.WriteComment("Assembly files");
            writer.WriteStartElement("Assemblies");

            Collection<Assembly> assemblies = AddLocalAssemblies(buildPath);
            //GetDependencyAssemblies(projectFile, ref assemblies);
            //AddLocalAssemblies(buildPath, ref assemblies);
            assemblies.Add(Assembly.Load(File.ReadAllBytes(buildPath + @"\" + targetDll)));

            foreach (Assembly assembly in assemblies)
            {
                ArrayList namespaceList = new ArrayList();

                writer.WriteStartElement("Assembly");
                writer.WriteAttributeString("Location", assembly.FullName.Substring(0, assembly.FullName.IndexOf(',')) + ".dll");
                writer.WriteAttributeString("DeploymentTarget", assemblyDeploymentLocation);

                foreach (Type t in assembly.GetExportedTypes())
                {
                    if (t.IsSubclassOf(typeof(System.Web.UI.WebControls.WebParts.WebPart)) && !namespaceList.Contains(t.Namespace))
                    {
                        namespaceList.Add(t.Namespace);
                    }
                }

                if (namespaceList.Count > 0)
                {
                    writer.WriteStartElement("SafeControls");  //<SafeControls>
                    foreach (string ns in namespaceList)
                    {
                        writer.WriteStartElement("SafeControl");  //</SafeControl>
                        writer.WriteAttributeString("Assembly", assembly.FullName);
                        writer.WriteAttributeString("Namespace", ns);
                        writer.WriteAttributeString("TypeName", "*");
                        writer.WriteAttributeString("Safe", true.ToString());
                        writer.WriteEndElement();  //</SafeControl>
                    }
                    writer.WriteEndElement();  //</SafeControls>
                }
                writer.WriteEndElement(); //</assembly>
            }
            writer.WriteEndElement(); // </Assemblies>

            assemblies.Clear();
            assemblies = null;
        }

        /// <summary>
        /// Cycle through the assemblies in the buildPath and include them in the solution.
        /// </summary>
        /// <param name="buildPath"></param>
        /// <param name="assemblies"></param>
        private static void AddLocalAssemblies(string buildPath, ref Collection<Assembly> assemblies)
        {
            string[] assemblyLocations = Directory.GetFiles(buildPath, "*.dll");
            foreach (string assemblyLocation in assemblyLocations)
            {
                Assembly assembly = Assembly.Load(File.ReadAllBytes(assemblyLocation));
                assemblies.Add(assembly);
                assembly = null;
            }
        }

        /// <summary>
        /// Cycle through the assemblies in the buildPath and include them in the solution
        /// </summary>
        /// <param name="buildPath">Path to the build output (bin) directory</param>
        /// <returns>A Collection of the Assemblies in the output directory</returns>
        private static Collection<Assembly> AddLocalAssemblies(string buildPath)
        {
            Collection<Assembly> assemblies = new Collection<Assembly>();
            string[] assemblyLocations = Directory.GetFiles(buildPath, "*.dll");
            foreach(string assemblyLocation in assemblyLocations)
            {
                assemblies.Add(Assembly.Load(File.ReadAllBytes(assemblyLocation)));
            }
            return assemblies;
        }

        static private void WriteFeatureManifestFile(XmlWriter writer, string path)
        {
            writer.WriteStartElement("FeatureManifest");
            writer.WriteAttributeString("Location", path);
            writer.WriteEndElement();
        }

        static private void WriteTemplateFiles(XmlWriter writer, string templatePath, string currentPath)
        {
            foreach (string file in Directory.GetFiles(currentPath))
            {
                if (!file.Contains("FEATURES"))
                {
                    writer.WriteStartElement("TemplateFile");
                    writer.WriteAttributeString("Location", file.Substring((templatePath + @"\").Length));
                    writer.WriteEndElement();
                }
            }

            foreach (string folder in Directory.GetDirectories(currentPath))
            {
                DirectoryInfo di = new DirectoryInfo(folder);
                if (!folder.Contains("FEATURES") && ((di.Attributes & FileAttributes.Hidden) != 0))
                {
                    WriteTemplateFiles(writer, templatePath, folder);
                }
            }
        }

        static private void WriteRootFiles(XmlWriter writer, string rootFileFolder, string currentPath)
        {
            foreach (string file in Directory.GetFiles(currentPath))
            {
                if (!file.Contains("TEMPLATE"))
                {
                    writer.WriteStartElement("RootFile");
                    writer.WriteAttributeString("Location", file.Substring((rootFileFolder + @"\").Length));
                    writer.WriteEndElement();
                }
            }

            foreach (string folder in Directory.GetDirectories(currentPath))
            {
                DirectoryInfo di = new DirectoryInfo(folder);
                if (!folder.Contains("TEMPLATE") && ((di.Attributes & FileAttributes.Hidden) != 0))
                {
                    WriteRootFiles(writer, rootFileFolder, folder);
                }
            }
        }

        private static void GetDependencyAssemblies(string projectPath, ref Collection<Assembly> referenceAssemblies)
        {
            XmlDocument projectDoc = new XmlDocument();
            projectDoc.Load(projectPath);

            XmlNamespaceManager ns = new XmlNamespaceManager(projectDoc.NameTable);
            ns.AddNamespace("stsdev", "http://schemas.microsoft.com/developer/msbuild/2003");

            XmlNodeList projectReferences = projectDoc.DocumentElement.SelectNodes(@"//stsdev:ProjectReference", ns);

            //Collection<Assembly> referenceAssemblies = new Collection<Assembly>();
            foreach (XmlNode projectReference in projectReferences)
            {
                string referenceProjectPath = projectReference.Attributes["Include"].Value;
                XmlDocument referenceProj = new XmlDocument();
                referenceProj.Load(referenceProjectPath);

                XmlNamespaceManager refns = new XmlNamespaceManager(referenceProj.NameTable);
                refns.AddNamespace("refvs", "http://schemas.microsoft.com/developer/msbuild/2003");

                XmlNode assemblyName = referenceProj.DocumentElement.SelectSingleNode(@"//refvs:AssemblyName", refns);
                string refassemblyName = string.Empty;
                if (assemblyName == null) //AssemblyName and Project Name are the same
                {
                    foreach (XmlNode child in projectReference.ChildNodes)
                    {
                        if (child.Name == "Name")
                            refassemblyName = child.InnerText;
                    }
                }
                else
                {
                    refassemblyName = assemblyName.InnerText;
                }

                string refProjectLocation = referenceProjectPath.Substring(0, referenceProjectPath.LastIndexOf('\\'));

                Assembly refAssembly;
                FindAssembly(refProjectLocation, refassemblyName + ".dll", out refAssembly);
                referenceAssemblies.Add(refAssembly);
                refAssembly = null;
            }

            XmlNodeList assemblyReferences = projectDoc.DocumentElement.SelectNodes(@"//stsdev:Reference", ns);
            foreach (XmlNode assemblyReference in assemblyReferences)
            {
                if (assemblyReference.HasChildNodes)
                {
                    try
                    {
                        Assembly includeAssembly = Assembly.Load(assemblyReference.Attributes["Include"].ToString());
                        referenceAssemblies.Add(includeAssembly);
                        includeAssembly = null;
                    }
                    catch
                    {
                        XmlNode hintPath = assemblyReference.SelectSingleNode(@"//stsdev:HintPath", ns);
                        if (hintPath != null)
                        {
                            Assembly refAssembly = Assembly.Load(File.ReadAllBytes(hintPath.InnerText));
                            referenceAssemblies.Add(refAssembly);
                            refAssembly = null;
                        }
                    }
                }
            }
        }

        private static void FindAssembly(string refProjectLocation, string refassemblyName, out Assembly foundAssembly)
        {
            if (File.Exists(refProjectLocation + "\\" + refassemblyName))
            {
                foundAssembly = Assembly.Load(File.ReadAllBytes(refProjectLocation + "\\" + refassemblyName));
            }
            else
            {
                foreach (string directory in Directory.GetDirectories(refProjectLocation))
                {
                    FindAssembly(directory, refassemblyName, out foundAssembly);
                    if (foundAssembly != null) return;
                }

                foundAssembly = null;
            }
        }
    }
}
